#version 330 core

struct Material {
  sampler2D	diffuse;
  sampler2D	specular;
  sampler2D emission;
  float		shininess;
};

struct DirLight {
  vec3	direction;

  vec3	ambient;
  vec3	diffuse;
  vec3	specular;
};

struct PointLight {
  vec3	position;

  float constant;
  float linear;
  float quadratic;

  vec3	ambient;
  vec3	diffuse;
  vec3	specular;
};
#define NR_POINT_LIGHTS 4

struct SpotLight {
  vec3	position;
  vec3	direction;
  float cutOff;
  float outerCutOff;
  
  float constant;
  float linear;
  float quadratic;
  
  vec3	ambient;
  vec3	diffuse;
  vec3	specular;
};

in vec3 FragPos;
in vec3 Normal;
in vec2 TexCoords;

uniform vec3		viewPos;
uniform Material	material;
uniform DirLight	dirLight;
uniform PointLight	pointLights[NR_POINT_LIGHTS];
uniform SpotLight	spotLight;

out vec4	color;

vec3	CalcDirLight(DirLight light, vec3 normal, vec3 viewDir);
vec3	CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir);
vec3	CalcSpotLight(SpotLight light, vec3 normal, vec3 fragPos, vec3 viewDir);

void main() {
  // Properties
  vec3	norm	= normalize(Normal);
  vec3	viewDir = normalize(viewPos - FragPos);

  // Phase 1: Directional lighting
  vec3	result	= CalcDirLight(dirLight, norm, viewDir);
  // Phase 2: Point lights
  for (int i = 0; i<NR_POINT_LIGHTS; i++) {
	result	   += CalcPointLight(pointLights[i], norm, FragPos, viewDir);
  }
  // Phase 3: Spot light
  result += CalcSpotLight(spotLight, norm, FragPos, viewDir);

  color = vec4(result, 1.0);
}

vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir) {
  vec3	lightDir   = normalize(-light.direction);
  // Diffuse shading
  float diff	   = max(dot(normal, lightDir), 0.0);
  // Specular shading
  vec3	reflectDir = reflect(-lightDir, normal);
  float spec	   = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
  // Combine results
  vec3	ambient	   = light.ambient * vec3(texture(material.diffuse, TexCoords));
  vec3	diffuse	   = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));
  vec3	specular   = light.specular * spec * vec3(texture(material.specular, TexCoords));
  return (ambient + diffuse + specular);
}

vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir) {
  vec3	lightDir	 = normalize(light.position - fragPos);
  // Diffuse shading
  float diff		 = max(dot(normal, lightDir), 0.0);
  // Specular shading
  vec3	reflectDir	 = reflect(-lightDir, normal);
  float spec		 = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
  // Attenuation
  float distance	 = length(light.position - fragPos);
  float attenuation	 = 1.0f / (light.constant + light.linear * distance +
							  light.quadratic * (distance * distance));
  // Combine results
  vec3	ambient		 = light.ambient * vec3(texture(material.diffuse, TexCoords));
  vec3	diffuse		 = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));
  vec3	specular	 = light.specular * spec * vec3(texture(material.specular, TexCoords));
  ambient			*= attenuation;
  diffuse			*= attenuation;
  specular			*= attenuation;
  return (ambient + diffuse + specular);
}

vec3	CalcSpotLight(SpotLight light, vec3 normal, vec3 fragPos, vec3 viewDir) {
  vec3	lightDir  = normalize(light.position - fragPos);
  float theta	  = dot(lightDir, normalize(-light.direction));
  float epsilon	  = light.cutOff - light.outerCutOff;
  float intensity = clamp((theta - light.outerCutOff) / epsilon, 0.0, 1.0);

  if (theta > light.outerCutOff) {
	// Attenuation
	float	distance   = length(light.position - fragPos);
	float attenuation  = 1.0f / (light.constant + light.linear * distance +
								light.quadratic * (distance * distance));
	// Diffuse shading
	float	diff	   = max(dot(normal, lightDir), 0.0);
	// Specular shading
	vec3	reflectDir = reflect(-lightDir, normal);
	float	spec	   = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);

	// Combine results
	vec3	ambient	 = light.ambient * vec3(texture(material.diffuse, TexCoords));
	vec3	diffuse	 = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));
	vec3	specular = light.specular * spec * vec3(texture(material.specular, TexCoords));
	
	ambient	 *= attenuation;
	diffuse	 *= attenuation;
	specular *= attenuation;
	
	ambient	 *= intensity;
	diffuse	 *= intensity;
	specular *= intensity;

	return (ambient + diffuse + specular);
  }
  else {
	return vec3(0.0);
  }
}

